﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Fun.FontDecode.OpenTypes
{
    // https://www.w3.org/Submission/EOT/#Version21
    public class EmbeddedOpenTypeParser : ParserBase
    {
        const byte XOR_KEY = 0x50;

        public class EmbeddedFontStruct
        {
            // 	Total structure length in bytes (including string and font data)
            public uint Filesize { get; internal set; }
            // Length of the OpenType font(FontData) in bytes
            public uint FontDataSize { get; internal set; }
            // 	Version number of this format - 0x00020001
            public uint FileVersion { get; internal set; }
            // Processing Flags
            public uint ProcessFlags { get; internal set; }
            // FontPANOSE
            public byte[] Panose { get; internal set; }
            // Charset
            public byte Charset { get; internal set; }
            // Italic
            public byte Italic { get; internal set; }
            // Weight
            public uint Weight { get; internal set; }
            // embedding permissions flags
            public ushort FsType { get; internal set; }
            // MagicNumber - 0x504C
            public ushort MagicNumber { get; internal set; }
            // UnicodeRange1、2、3、4
            public uint UnicodeRange1 { get; internal set; }
            public uint UnicodeRange2 { get; internal set; }
            public uint UnicodeRange3 { get; internal set; }
            public uint UnicodeRange4 { get; internal set; }
            // CodePageRange1、2
            public uint CodePageRange1 { get; internal set; }
            public uint CodePageRange2 { get; internal set; }
            // 	CheckSumAdjustment
            public uint Checksum { get; internal set; }
            // Reserved1、2、3、4, must be 0
            public uint Reserved1 { get; internal set; }
            public uint Reserved2 { get; internal set; }
            public uint Reserved3 { get; internal set; }
            public uint Reserved4 { get; internal set; }
            // Padding 1, always be 0x0000
            public ushort Padding1 { get; internal set; }
            // UTF-16, English language Font Family
            public string FamilyName { get; internal set; }
            // Padding 2, always be 0x0000
            public ushort Padding2 { get; internal set; }
            // UTF-16, English language Font Subfamily
            public string StyleName { get; internal set; }
            // Padding 3, always be 0x0000
            public ushort Padding3 { get; internal set; }
            // UTF-16, English language Version
            public string VersionName { get; internal set; }
            // Padding 4, always be 0x0000
            public ushort Padding4 { get; internal set; }
            // UTF-16, English language full name
            public string FullName { get; internal set; }
            // Padding 5, always be 0x0000
            public ushort Padding5 { get; internal set; }
            // UTF-16, English language RootString
            public string RootString { get; internal set; }
            // RootStringCheckSum
            public uint RootStringChecksum { get; internal set; }
            // Codepage value needed for EUDC font support
            public uint EducCodePage { get; internal set; }
            // Padding 6, always be 0x0000
            public ushort Padding6 { get; internal set; }
            // Currently reserved
            public string Signature { get; internal set; }
            // Processing flags for the EUDC font
            public uint EducFlags { get; internal set; }
            // EDUC FontSize
            public uint EducFontSize { get; internal set; }
            // Currently reserved
            public byte[] EducFontData { get; internal set; }
        }

        public EmbeddedFontStruct FontStruct { get; private set; }
        public byte[] FontData { get; private set; }

        public override void Parse(BinaryReader reader)
        {
            var rec = new EmbeddedFontStruct();

            // 	Total structure length in bytes (including string and font data)
            rec.Filesize = reader.ReadUInt32();
            if (rec.Filesize != reader.BaseStream.Length)
                throw new Exception("EOT：文件已损坏。");
            // Length of the OpenType font(FontData) in bytes
            rec.FontDataSize = reader.ReadUInt32();
            // 	Version number of this format - 0x00020001
            rec.FileVersion = reader.ReadUInt32();
            if (rec.FileVersion != 0x00010000 && rec.FileVersion != 0x00020001 && rec.FileVersion != 0x00020002)
                throw new Exception($"EOT：不支持此版本的文件。 ({ rec.FileVersion.ToString("X2") })");
            // Processing Flags
            rec.ProcessFlags = reader.ReadUInt32();
            // FontPANOSE
            rec.Panose = reader.ReadBytes(10);
            // Charset
            rec.Charset = reader.ReadByte();
            // Italic
            rec.Italic = reader.ReadByte();
            // Weight
            rec.Weight = reader.ReadUInt32();
            // embedding permissions flags
            rec.FsType = reader.ReadUInt16();
            // MagicNumber - 0x504C
            rec.MagicNumber = reader.ReadUInt16();
            if (rec.MagicNumber != 0x504C)
                throw new Exception("EOT：非法的文件格式。");
            // UnicodeRange1、2、3、4
            rec.UnicodeRange1 = reader.ReadUInt32();
            rec.UnicodeRange2 = reader.ReadUInt32();
            rec.UnicodeRange3 = reader.ReadUInt32();
            rec.UnicodeRange4 = reader.ReadUInt32();
            // CodePageRange1、2
            rec.CodePageRange1 = reader.ReadUInt32();
            rec.CodePageRange2 = reader.ReadUInt32();
            // 	CheckSumAdjustment
            rec.Checksum = reader.ReadUInt32();
            // Reserved1、2、3、4, must be 0
            rec.Reserved1 = reader.ReadUInt32();
            rec.Reserved2 = reader.ReadUInt32();
            rec.Reserved3 = reader.ReadUInt32();
            rec.Reserved4 = reader.ReadUInt32();
            // Padding 1, always be 0x0000
            rec.Padding1 = reader.ReadUInt16();
            // UTF-16, English language Font Family
            rec.FamilyName = ReadUnicodeString(reader);
            // Padding 2, always be 0x0000
            rec.Padding2 = reader.ReadUInt16();
            // UTF-16, English language Font Subfamily
            rec.StyleName = ReadUnicodeString(reader);
            // Padding 3, always be 0x0000
            rec.Padding3 = reader.ReadUInt16();
            // UTF-16, English language Version
            rec.VersionName = ReadUnicodeString(reader);
            // Padding 4, always be 0x0000
            rec.Padding4 = reader.ReadUInt16();
            // UTF-16, English language full name
            rec.FullName = ReadUnicodeString(reader);
            // 0x00020001
            if (rec.FileVersion >= 0x00020001)
            {
                // Padding 5, always be 0x0000
                rec.Padding5 = reader.ReadUInt16();
                // UTF-16, English language RootString
                rec.RootString = ReadUnicodeString(reader);
            }
            // 0x00020002
            if (rec.FileVersion >= 0x00020002)
            {
                // RootStringCheckSum
                rec.RootStringChecksum = reader.ReadUInt32();
                // Codepage value needed for EUDC font support
                rec.EducCodePage = reader.ReadUInt32();
                // Padding 6, always be 0x0000
                rec.Padding6 = reader.ReadUInt16();
                // Currently reserved
                rec.Signature = ReadUnicodeString(reader);
                // Processing flags for the EUDC font
                rec.EducFlags = reader.ReadUInt32();
                // Padding 6, always be 0x0000
                rec.EducFontSize = reader.ReadUInt32();
                // Currently reserved
                rec.EducFontData = reader.ReadBytes((int)rec.EducFontSize);
            }

            // data for this EOT file. The data may be compressed 
            // or XOR encrypted as indicated by the processing flags
            this.FontData = reader.ReadBytes((int)rec.FontDataSize);
            this.FontStruct = rec;
        }

        string ReadUnicodeString(BinaryReader reader)
        {
            // Number of bytes used by the array
            var size = reader.ReadUInt16();
            if (size == 0)
                return string.Empty;

            // UTF-16
            var bytes = reader.ReadBytes(size);
            return Encoding.Unicode.GetString(bytes);
        }
    }
}
